using System.Collections.Specialized;

namespace BizUnit.Extensions.BizTalk.Helpers
{
    using System;
    using System.IO;
    using System.Reflection;
    using System.Xml;
    using System.Xml.XPath;
    using System.Xml.Xsl;

    using Microsoft.XLANGs.RuntimeTypes;
    /// <summary>
    /// All credit and inspiration goes to: 
    /// How do I Reuse BizTalk 2004 Maps Outside of BizTalk? Gilles Zunino
    /// ESB Guidance: Marty Waz, Brian Loesgen et al
    /// </summary>
    public class MapHelper
    {

        public static string TransformMessage(string msgString, string mapTypeName)
        {
            return TransformMessage(msgString, GetMapType(mapTypeName));
        }

        public static string TransformMessage(string msgString, string mapTypeName, string assemblyPath)
        {
            Assembly assembly = LoadAssembly(assemblyPath);
            return TransformMessage(msgString, mapTypeName, assembly);
        }

        public static string TransformMessage(string msgString, string mapTypeName, Assembly assembly)
        {
            return TransformMessage(msgString, GetMapType(mapTypeName, assembly));
        }

        public static string TransformMessage(string msgString, Type mapType)
        {
            XmlDocument msg = new XmlDocument();
            msg.LoadXml(msgString);
            XmlDocument xmlDocOutputMsg;
            try
            {
                XslTransform transform = TransformMetaData.For(mapType).Transform;
                XsltArgumentList argList = TransformMetaData.For(mapType).ArgumentList;
                xmlDocOutputMsg = MapHelper.DoTransformation(transform, argList, msg);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("Error transforming message", ex);
            }
            return xmlDocOutputMsg.InnerXml;
        }


        public static Type GetMapType(string typeName)
        {
            Type mapType = Type.GetType(typeName);
            if (null == mapType)
                throw new InvalidOperationException("Unable to retrieve map " + typeName);
            return mapType;
        }

        public static Type GetMapType(string typeName, Assembly assembly)
        {
            Type ty = null;
            ty = assembly.GetType(typeName, true, false);
            return ty;
        }


        private static Assembly LoadAssembly(string assemblyPath)
        {
            return Assembly.LoadFrom(assemblyPath);
        }

        public static XmlDocument DoTransformation(string xsltPath,
                                                   NameValueCollection extensionObjects,
                                                   NameValueCollection xsltParams,
                                                   XmlDocument sourceDoc)
        {
            //Add some param defence
            XslTransform xslt = new XslTransform();
            xslt.Load(xsltPath);

            XsltArgumentList xsltArgs = new XsltArgumentList();
            if (extensionObjects != null || extensionObjects.Count == 0)
            {
                foreach (string s in extensionObjects.AllKeys)
                {
                    xsltArgs.AddExtensionObject(s, Activator.CreateInstance(Type.GetType(extensionObjects[s])));
                }
            }

            if (xsltParams != null || xsltParams.Count == 0)
            {
                foreach (string s in xsltParams.AllKeys)
                {
                    xsltArgs.AddParam(s, string.Empty, xsltParams[s]);
                }
            }
            return DoTransformation(xslt, xsltArgs, sourceDoc);
        }


        public static XmlDocument DoTransformation(XslTransform xslt, XsltArgumentList args, XmlDocument sourceDoc)
        {
            XmlNodeReader sourceRoot = new XmlNodeReader(sourceDoc);
            XPathDocument srcXPath = new XPathDocument(sourceRoot);
            MemoryStream stm = new MemoryStream();

            XmlTextWriter writer = new XmlTextWriter(stm, System.Text.Encoding.Unicode);

            XmlTextReader destReader;
            XmlDocument destDoc = new XmlDocument();

            try
            {
                xslt.Transform(srcXPath, args, writer, null);

                stm.Flush();
                stm.Seek(0, SeekOrigin.Begin);

                destReader = new XmlTextReader(stm);
                destDoc.Load(destReader);
            }
            finally
            {
                stm.Close();
                stm.Dispose();
            }

            return destDoc;
        }
    }
}
